using System;

public enum FieldType{
    Unknown,
    Integer,
    Number,
    String,
    Bool,
    // Array,
    // Selection,
}

public class FieldOption<T>{
    public string Name { get; set; }
    public T Value { get; set; }
    // public FieldType ValueType { get; set; }

    public FieldOption(string name,T value)
    {
        Name=name;
        Value=value;
        // ValueType=typeof(T).Name switch {
        //     "Int32" =>FieldType.Integer,
        //     "String" =>FieldType.String,
        //     _ =>FieldType.Unknown,
        // };
    }
}

[AttributeUsage(AttributeTargets.Property,Inherited =true)]
public class FieldAttribute:Attribute{
    public bool Required { get; set; }
    public string Detail { get; set; }
    public FieldType Type { get; set; }
    public string? OptionUrl { get; set; }


    public FieldAttribute(FieldType type,string detail,bool required=false,string? option_url=null)
    {
        Type=type;
        Required=required;
        Detail=detail;
        OptionUrl=option_url;
    }
}
// [AttributeUsage(AttributeTargets.Property)]
// public class SelectionFieldAttribute:FieldAttribute{
//     public SelectionFieldAttribute(string option_url,string detail,bool required=false):base(FieldType.Selection,detail,required)
//     {
//         OptionUrl=option_url;
//     }

//     public string OptionUrl {get;set;}
// }

public record FieldInfo{
    public string Detail { get; init; }
    public string Name { get; init; }
    public bool Required { get; init; }
    public FieldType Type { get;init; }
    public string? OptionUrl { get; set; }

    public FieldInfo(string name,FieldType type,string detail,bool required=false, string? option_url=null)
    {
        Name=name;
        Type=type;
        Required=required;
        Detail=detail;
        OptionUrl=option_url;
    }

    public FieldInfo(string name,FieldAttribute attr):this(name,attr.Type,attr.Detail,attr.Required,attr.OptionUrl){

    }

}

public static class Schema{
    public static FieldInfo[] GetSchema(Type t){
        var list=new List<FieldInfo>();
        var properties=t.GetProperties();
        foreach (var prop in properties)
        {
            var attrs=prop.GetCustomAttributes(typeof(FieldAttribute),true);
            if (attrs.Length==0)
            {
                continue;
            }
            var field=(FieldAttribute)attrs[0];
            list.Add(new FieldInfo(prop.Name,field));
        }
        return list.ToArray();
    }

    public static void Update(object source, object target){
        var source_type=source.GetType();
        var target_type=target.GetType();
        var fields=GetSchema(source_type);
        foreach (var field in fields)
        {
            var new_val=source_type.GetProperty(field.Name)?.GetValue(source,null);
            if (new_val is null && field.Required)
            {
                throw new Exception($"{field.Name} can not be empty!");
            }
            var old_val=target_type.GetProperty(field.Name)?.GetValue(target,null);
            if (Convert.Equals(new_val, old_val)){
                continue;
            }
            target_type.GetProperty(field.Name)?.SetValue(target,new_val);
        }
    }
}